package xyz.chatboxapp.chatbox;

import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.view.WindowManager;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.View;
import android.view.MotionEvent;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.graphics.Color;
import android.util.DisplayMetrics;
import android.animation.ValueAnimator;
import android.webkit.WebView;
import android.webkit.WebSettings;
import android.webkit.WebChromeClient;
import android.webkit.ConsoleMessage;
import android.webkit.JavascriptInterface;
import android.widget.FrameLayout;
import android.view.ViewGroup;
import android.content.ClipboardManager;
import android.content.ClipData;
import android.widget.Toast;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.SystemClock;
import java.util.concurrent.TimeUnit;

import org.json.JSONArray;
import org.json.JSONObject;

import com.getcapacitor.JSObject;
import com.getcapacitor.Plugin;
import com.getcapacitor.PluginCall;
import com.getcapacitor.PluginMethod;
import com.getcapacitor.annotation.CapacitorPlugin;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

@CapacitorPlugin(name = "SystemOverlay")
public class SystemOverlayPlugin extends Plugin {
    
    private WindowManager windowManager;
    private View overlayView;
    private WindowManager.LayoutParams params;
    private View bubbleView;
    private boolean isExpanded = false; // kept for compatibility; native panel is disabled
    private int lastBubbleX = 0;
    private int lastBubbleY = 0;
    // Store bubble dimensions and screen density for accurate restore and positioning
    private int bubbleWidthPx = 0;
    private int bubbleHeightPx = 0;
    private float screenDensity = 1f;
    // When user clicks bubble to open app, JS listener may not be ready. Keep a pending flag for JS to query on mount.
    private boolean pendingToggle = false;
    // Hard-disable any native expanded panel. Only JS-driven panel is allowed.
    private static final boolean ALLOW_NATIVE_PANEL = false;
    // WebView overlay state
    private View overlayWebContainer;
    private WebView overlayWebView;
    private WindowManager.LayoutParams overlayWebParams;
    private boolean overlayWebVisible = false;
    private int overlayWebLastX = 0;
    private int overlayWebLastY = 0;
    private String pendingInput = null;
    private static final String PREFS_NAME = "overlay_prefs";
    private static final String PREF_POS_X = "pos_x";
    private static final String PREF_POS_Y = "pos_y";
    private static final String PREF_AI_CONFIG = "overlay_ai_config";
    private OkHttpClient httpClient;
    // Drag handle & dynamic exclusions
    private View overlayDragHandle;
    private int dragExcludeLeftPx = 0;  // dynamic: left area to pass-through clicks (title + model selector)
    private int dragExcludeRightPx = 0; // dynamic: right area to pass-through clicks (close button)

    private SharedPreferences getPrefs() {
        try { return getContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE); } catch (Exception e) { return null; }
    }

    @Override
    public void load() {
        super.load();
        Log.d("SystemOverlay", "SystemOverlayPlugin loaded. bridge=" + (getBridge() != null));
    }

    private OkHttpClient getHttp() {
        if (httpClient == null) {
            httpClient = new OkHttpClient.Builder()
                    .connectTimeout(20, TimeUnit.SECONDS)
                    .readTimeout(60, TimeUnit.SECONDS)
                    .build();
        }
        return httpClient;
    }

    private void evalInOverlayWeb(String js) {
        try {
            new Handler(Looper.getMainLooper()).post(() -> {
                try {
                    if (overlayWebView != null) {
                        overlayWebView.evaluateJavascript(js, null);
                    }
                } catch (Exception ignored) {}
            });
        } catch (Exception ignored) {}
    }

    // Helper: clamp integer within [min, max]
    private int clamp(int val, int min, int max) {
        return Math.max(min, Math.min(max, val));
    }

    @PluginMethod
    public void checkOverlayPermission(PluginCall call) {
        JSObject ret = new JSObject();
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            ret.put("hasPermission", Settings.canDrawOverlays(getContext()));
        } else {
            ret.put("hasPermission", true);
        }
        
        call.resolve(ret);
    }

    @PluginMethod
    public void requestOverlayPermission(PluginCall call) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (!Settings.canDrawOverlays(getContext())) {
                try {
                    Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                            Uri.parse("package:" + getContext().getPackageName()));
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    getContext().startActivity(intent);
                    
                    JSObject ret = new JSObject();
                    ret.put("granted", false);
                    ret.put("message", "请在设置中允许悬浮窗权限，然后返回应用重试");
                    call.resolve(ret);
                } catch (Exception e) {
                    call.reject("无法打开权限设置页面: " + e.getMessage());
                }
                return;
            }
        }
        
        JSObject ret = new JSObject();
        ret.put("granted", true);
        call.resolve(ret);
    }

    @PluginMethod
    public void showSystemOverlay(PluginCall call) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(getContext())) {
            call.reject("No overlay permission");
            return;
        }

        getActivity().runOnUiThread(() -> {
            try {
                final Context appCtx = getContext().getApplicationContext();
                windowManager = (WindowManager) appCtx.getSystemService(Context.WINDOW_SERVICE);
                // Ensure previous overlay is removed to avoid stacking
                try {
                    if (overlayView != null) {
                        windowManager.removeView(overlayView);
                        overlayView = null;
                    }
                } catch (Exception ignored) {}
                isExpanded = false;
                
                // 读取期望尺寸（单位：dp），并转换为 px
                int dpWidth = call.getInt("width", 56);
                int dpHeight = call.getInt("height", 56);
                screenDensity = appCtx.getResources().getDisplayMetrics().density;
                int widthPx = Math.max(32, (int) (dpWidth * screenDensity));
                int heightPx = Math.max(32, (int) (dpHeight * screenDensity));
                // cache for restore when collapsing from panel
                bubbleWidthPx = widthPx;
                bubbleHeightPx = heightPx;
                
                // 创建图标按钮作为悬浮球
                ImageButton floatingButton = new ImageButton(appCtx);
                floatingButton.setBackgroundColor(Color.TRANSPARENT);
                floatingButton.setScaleType(ImageView.ScaleType.FIT_CENTER);
                floatingButton.setAdjustViewBounds(true);
                floatingButton.setContentDescription("System Overlay");
                try {
                    floatingButton.setImageResource(R.mipmap.ic_launcher_round);
                } catch (Exception e1) {
                    try {
                        floatingButton.setImageResource(R.mipmap.ic_launcher);
                    } catch (Exception e2) {
                        // 保底：若资源缺失，设置为无图标
                    }
                }
                
                // 设置布局参数
                int layoutFlag;
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    layoutFlag = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
                } else {
                    layoutFlag = WindowManager.LayoutParams.TYPE_PHONE;
                }
                
                params = new WindowManager.LayoutParams(
                    widthPx,
                    heightPx,
                    layoutFlag,
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
                    PixelFormat.TRANSLUCENT
                );
                
                params.gravity = Gravity.TOP | Gravity.LEFT;
                params.x = call.getInt("x", 100);
                params.y = call.getInt("y", 100);

                // 若未传入位置参数，则默认放到右侧中间并吸边
                try {
                    boolean hasX = call.getData() != null && call.getData().has("x");
                    boolean hasY = call.getData() != null && call.getData().has("y");
                    if (!hasX && !hasY) {
                        DisplayMetrics dm = appCtx.getResources().getDisplayMetrics();
                        int screenW = dm.widthPixels;
                        int screenH = dm.heightPixels;
                        int marginPx = (int) (10 * screenDensity);
                        params.x = screenW - widthPx - marginPx;
                        params.y = Math.max(marginPx, (screenH - heightPx) / 2);
                    }
                } catch (Exception ignored) {}
                
                // 添加拖拽功能
                floatingButton.setOnTouchListener(new View.OnTouchListener() {
                    private int initialX, initialY;
                    private float initialTouchX, initialTouchY;
                    private boolean moved;
                    private final int marginPx = (int) (10 * screenDensity);
                    
                    private int clamp(int val, int min, int max) {
                        return Math.max(min, Math.min(max, val));
                    }

                    private void animateToX(int targetX) {
                        final int startX = params.x;
                        ValueAnimator animator = ValueAnimator.ofInt(startX, targetX);
                        animator.setDuration(180);
                        animator.addUpdateListener(a -> {
                            params.x = (int) a.getAnimatedValue();
                            try { windowManager.updateViewLayout(floatingButton, params); } catch (Exception ignored) {}
                        });
                        animator.start();
                    }

                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        DisplayMetrics dm = appCtx.getResources().getDisplayMetrics();
                        int screenW = dm.widthPixels;
                        int screenH = dm.heightPixels;
                        switch (event.getAction()) {
                            case MotionEvent.ACTION_DOWN:
                                initialX = params.x;
                                initialY = params.y;
                                initialTouchX = event.getRawX();
                                initialTouchY = event.getRawY();
                                moved = false;
                                return true;
                                
                            case MotionEvent.ACTION_MOVE:
                                int nextX = initialX + (int) (event.getRawX() - initialTouchX);
                                int nextY = initialY + (int) (event.getRawY() - initialTouchY);
                                // 约束在屏幕内，保留边距
                                params.x = clamp(nextX, marginPx, screenW - params.width - marginPx);
                                params.y = clamp(nextY, marginPx, screenH - params.height - marginPx);
                                windowManager.updateViewLayout(floatingButton, params);
                                lastBubbleX = params.x;
                                lastBubbleY = params.y;
                                moved = true;
                                return true;
                                
                            case MotionEvent.ACTION_UP:
                                float dx = Math.abs(event.getRawX() - initialTouchX);
                                float dy = Math.abs(event.getRawY() - initialTouchY);
                                boolean isClick = dx < 10 && dy < 10;

                                if (isClick) {
                                    // 点击：切换系统级 Overlay WebView（跨应用层显示）
                                    try {
                                        new Handler(Looper.getMainLooper()).post(() -> toggleOverlayWeb());
                                    } catch (Exception ignored) {}
                                    return true;
                                } else {
                                    // 松手后自动吸附左右边缘，并广播位置
                                    int mid = (screenW - params.width) / 2;
                                    int targetX = params.x <= mid ? marginPx : (screenW - params.width - marginPx);
                                    animateToX(targetX);

                                    JSObject data = new JSObject();
                                    data.put("x", targetX);
                                    data.put("y", params.y);
                                    notifyListeners("overlayMoved", data);
                                    lastBubbleX = targetX;
                                    lastBubbleY = params.y;
                                }
                                return true;
                        }
                        return false;
                    }
                });
                
                overlayView = floatingButton;
                bubbleView = floatingButton;
                windowManager.addView(overlayView, params);
                
                call.resolve();
            } catch (Exception e) {
                call.reject("Failed to show overlay: " + e.getMessage());
            }
        });
    }

    private void toggleExpandedPanel() {
        // Disabled: native big panel is not allowed anymore.
        Log.w("SystemOverlay", "toggleExpandedPanel() called but native panel is disabled");
        return;
    }

    private void expandPanel() {
        // Disabled: native big panel is not allowed anymore.
        Log.w("SystemOverlay", "expandPanel() called but native panel is disabled");
        return;
    }

    private void collapsePanel() {
        // Disabled: native big panel is not allowed anymore.
        Log.w("SystemOverlay", "collapsePanel() called but native panel is disabled");
        isExpanded = false;
        return;
    }

    // ===== Overlay WebView (system-level) =====
    private void showOverlayWeb() {
        try {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(getContext())) {
                Log.e("SystemOverlay", "No overlay permission for WebView overlay");
                return;
            }

            final Context appCtx = getContext().getApplicationContext();
            if (windowManager == null) {
                windowManager = (WindowManager) appCtx.getSystemService(Context.WINDOW_SERVICE);
            }
            // Ensure density is up-to-date for dp/px conversions used by drag logic
            try { screenDensity = appCtx.getResources().getDisplayMetrics().density; } catch (Exception ignored) {}

            if (overlayWebVisible && overlayWebContainer != null) {
                return;
            }

            // Create container
            FrameLayout container = new FrameLayout(appCtx);
            container.setBackgroundColor(Color.TRANSPARENT);

            // Prepare WebView
            WebView web = new WebView(appCtx);
            web.setBackgroundColor(Color.TRANSPARENT);
            WebSettings ws = web.getSettings();
            ws.setJavaScriptEnabled(true);
            ws.setDomStorageEnabled(true);
            ws.setAllowFileAccess(true);
            ws.setAllowContentAccess(true);
            // Ensure focus so IME can appear when inputs are focused
            web.setFocusable(true);
            web.setFocusableInTouchMode(true);
            container.setFocusable(true);
            container.setFocusableInTouchMode(true);

            // Bridge & console forwarding
            web.addJavascriptInterface(new OverlayJsBridge(), "OverlayBridge");
            try {
                web.setWebChromeClient(new WebChromeClient() {
                    @Override public boolean onConsoleMessage(ConsoleMessage cm) {
                        try { Log.d("SystemOverlay", "[JS] " + cm.message() + " @" + cm.sourceId() + ":" + cm.lineNumber()); } catch (Exception ignored) {}
                        return super.onConsoleMessage(cm);
                    }
                });
            } catch (Exception ignored) {}
            web.loadUrl("file:///android_asset/overlay/index.html");
            // Add WebView to container
            FrameLayout.LayoutParams wlp = new FrameLayout.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT
            );
            wlp.gravity = Gravity.TOP;
            container.addView(web, wlp);

            // Add a native drag handle at top-left (fixed size) to reliably capture drag
            final View dragHandle = new View(appCtx);
            dragHandle.setBackgroundColor(Color.TRANSPARENT);
            FrameLayout.LayoutParams hp = new FrameLayout.LayoutParams(
                    (int) (96 * screenDensity), // narrower fixed width to not block selector
                    (int) (56 * screenDensity)
            );
            hp.gravity = Gravity.TOP | Gravity.LEFT;
            hp.setMargins(0, 0, 0, 0);
            container.addView(dragHandle, hp);
            overlayDragHandle = dragHandle;

            dragHandle.setOnTouchListener(new View.OnTouchListener() {
                private final int touchSlopPx = (int) (8 * screenDensity);
                private float downRawX, downRawY;
                private int initialX, initialY;
                private boolean dragging = false;
                private boolean pressed = false;
                private boolean forwardedDown = false;
                private long forwardedDownTime = 0L;

                @Override public boolean onTouch(View v, MotionEvent e) {
                    try {
                        if (overlayWebParams == null) return false;
                    } catch (Exception ignored) {}

                    switch (e.getAction()) {
                        case MotionEvent.ACTION_DOWN: {
                            // The handle view margins already exclude left/right areas.
                            // Intercept drags anywhere within the handle's bounds.
                            downRawX = e.getRawX();
                            downRawY = e.getRawY();
                            initialX = overlayWebParams.x;
                            initialY = overlayWebParams.y;
                            dragging = false;
                            pressed = true;
                            // Forward DOWN to WebView so clicks can start normally under the handle
                            try {
                                if (overlayWebView != null) {
                                    int[] loc = new int[2];
                                    overlayWebView.getLocationOnScreen(loc);
                                    float x = downRawX - loc[0];
                                    float y = downRawY - loc[1];
                                    forwardedDownTime = e.getDownTime();
                                    MotionEvent downEv = MotionEvent.obtain(forwardedDownTime, e.getEventTime(), MotionEvent.ACTION_DOWN, x, y, 0);
                                    overlayWebView.dispatchTouchEvent(downEv);
                                    downEv.recycle();
                                    forwardedDown = true;
                                    try { Log.d("SystemOverlay", "dragHandle DOWN passthrough x=" + x + " y=" + y); } catch (Exception ignored2) {}
                                }
                            } catch (Exception ignored) {}
                            try { Log.d("SystemOverlay", "dragHandle DOWN rawX=" + downRawX + " rawY=" + downRawY + " initialY=" + initialY + " handleW=" + v.getWidth()); } catch (Exception ignored) {}
                            return true; // intercept
                        }
                        case MotionEvent.ACTION_MOVE: {
                            if (pressed && !dragging) {
                                float dx = Math.abs(e.getRawX() - downRawX);
                                float dy = Math.abs(e.getRawY() - downRawY);
                                if (dx > touchSlopPx || dy > touchSlopPx) {
                                    dragging = true; // start drag
                                    // Cancel underlying click if we started dragging
                                    if (forwardedDown && overlayWebView != null) {
                                        try {
                                            int[] loc = new int[2];
                                            overlayWebView.getLocationOnScreen(loc);
                                            float x = e.getRawX() - loc[0];
                                            float y = e.getRawY() - loc[1];
                                            long now = SystemClock.uptimeMillis();
                                            MotionEvent cancelEv = MotionEvent.obtain(forwardedDownTime, now, MotionEvent.ACTION_CANCEL, x, y, 0);
                                            overlayWebView.dispatchTouchEvent(cancelEv);
                                            cancelEv.recycle();
                                        } catch (Exception ignored) {}
                                        forwardedDown = false;
                                    }
                                    try { Log.d("SystemOverlay", "dragHandle START_DRAG dx=" + dx + " dy=" + dy); } catch (Exception ignored) {}
                                }
                                return true;
                            }
                            if (!dragging) return true;
                            int ny = initialY + (int)(e.getRawY() - downRawY);
                            int screenH = appCtx.getResources().getDisplayMetrics().heightPixels;
                            int marginPx = (int) (10 * screenDensity);
                            int maxY = Math.max(marginPx, screenH - container.getHeight() - marginPx);
                            overlayWebParams.x = 0; // full-width, fixed x
                            overlayWebParams.y = clamp(ny, marginPx, maxY);
                            try { windowManager.updateViewLayout(container, overlayWebParams); } catch (Exception ignored) {}
                            try { Log.d("SystemOverlay", "dragHandle MOVE y=" + overlayWebParams.y + " maxY=" + maxY); } catch (Exception ignored) {}
                            return true;
                        }
                        case MotionEvent.ACTION_UP:
                        case MotionEvent.ACTION_CANCEL: {
                            if (!dragging) {
                                pressed = false;
                                // Complete the tap on WebView with an UP if we previously forwarded DOWN
                                try {
                                    if (forwardedDown && overlayWebView != null) {
                                        int[] loc = new int[2];
                                        overlayWebView.getLocationOnScreen(loc);
                                        float x = e.getRawX() - loc[0];
                                        float y = e.getRawY() - loc[1];
                                        long now = SystemClock.uptimeMillis();
                                        MotionEvent upEv = MotionEvent.obtain(forwardedDownTime, now, MotionEvent.ACTION_UP, x, y, 0);
                                        overlayWebView.dispatchTouchEvent(upEv);
                                        upEv.recycle();
                                        try { Log.d("SystemOverlay", "dragHandle TAP-UP passthrough x=" + x + " y=" + y); } catch (Exception ignored2) {}
                                    }
                                } catch (Exception ignored) {}
                                forwardedDown = false;
                                return true;
                            }
                            overlayWebParams.x = 0; // always stick to left for full-width
                            overlayWebLastX = 0; overlayWebLastY = overlayWebParams.y;
                            try {
                                SharedPreferences p = getPrefs();
                                if (p != null) p.edit().putInt(PREF_POS_X, 0).putInt(PREF_POS_Y, overlayWebLastY).apply();
                            } catch (Exception ignored) {}
                            dragging = false;
                            pressed = false;
                            try { Log.d("SystemOverlay", "dragHandle UP y=" + overlayWebParams.y); } catch (Exception ignored) {}
                            return true;
                        }
                    }
                    return false;
                }
            });

            // Window params
            int layoutFlag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE;
            overlayWebParams = new WindowManager.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT,
                    layoutFlag,
                    // Make window focusable to allow input & IME; keep NOT_TOUCH_MODAL so outside taps pass through
                    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                    PixelFormat.TRANSLUCENT
            );
            overlayWebParams.gravity = Gravity.TOP | Gravity.LEFT;
            // load saved position if available
            try {
                SharedPreferences p = getPrefs();
                if (p != null) {
                    overlayWebLastX = p.getInt(PREF_POS_X, overlayWebLastX);
                    overlayWebLastY = p.getInt(PREF_POS_Y, overlayWebLastY);
                }
            } catch (Exception ignored) {}
            overlayWebParams.x = overlayWebLastX;
            // Default Y to mid-screen if no saved value (avoid top status bar)
            int screenH = appCtx.getResources().getDisplayMetrics().heightPixels;
            int safeTopMargin = (int) (80 * screenDensity); // keep away from status bar / notches
            int defaultY = Math.max(safeTopMargin, screenH / 3);
            overlayWebParams.y = overlayWebLastY > 0 ? overlayWebLastY : defaultY;
            overlayWebParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;

            overlayWebContainer = container;
            overlayWebView = web;
            windowManager.addView(overlayWebContainer, overlayWebParams);
            try { overlayWebView.requestFocus(View.FOCUS_DOWN); } catch (Exception ignored) {}
            overlayWebVisible = true;
            Log.d("SystemOverlay", "Overlay WebView shown");
        } catch (Exception e) {
            Log.e("SystemOverlay", "showOverlayWeb error: " + e.getMessage());
        }
    }

    private void hideOverlayWeb() {
        try {
            if (overlayWebContainer != null && windowManager != null) {
                windowManager.removeView(overlayWebContainer);
            }
            overlayWebContainer = null;
            overlayWebView = null;
            overlayWebVisible = false;
            Log.d("SystemOverlay", "Overlay WebView hidden");
        } catch (Exception e) {
            Log.e("SystemOverlay", "hideOverlayWeb error: " + e.getMessage());
            // 重置标志位以确保下次能正确显示
            overlayWebVisible = false;
        }
    }

    private void toggleOverlayWeb() {
        if (overlayWebVisible) hideOverlayWeb(); else showOverlayWeb();
    }

    // Ensure drag handle keeps a minimum draggable center width by clamping left/right margins
    private void applyDragMarginsImmediate() {
        try {
            int screenW = 0;
            try { screenW = getContext().getResources().getDisplayMetrics().widthPixels; } catch (Exception ignored) {}
            if (screenW <= 0) return;
            int minCenterPx = (int) (160 * screenDensity); // keep at least 160dp center grip
            int left = Math.max(0, dragExcludeLeftPx);
            int right = Math.max(0, dragExcludeRightPx);
            int maxSum = Math.max(0, screenW - minCenterPx);
            if (left + right > maxSum) {
                float scale = maxSum <= 0 ? 0f : ((float) maxSum) / (left + right);
                left = Math.round(left * scale);
                right = Math.max(0, maxSum - left);
            }
            try {
                if (overlayDragHandle != null) {
                    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) overlayDragHandle.getLayoutParams();
                    if (lp != null) {
                        lp.leftMargin = left;
                        lp.rightMargin = right;
                        overlayDragHandle.setLayoutParams(lp);
                        overlayDragHandle.requestLayout();
                    }
                }
            } catch (Exception ignored) {}
            try { Log.i("SystemOverlay", "applyDragMargins left=" + left + " right=" + right + " screenW=" + screenW + " center=" + (screenW - left - right)); } catch (Exception ignored) {}
        } catch (Exception ignored) {}
    }

    private class OverlayJsBridge {
        @JavascriptInterface public String getInitialState() {
            try {
                JSONObject obj = new JSONObject();
                // position
                JSONObject pos = new JSONObject();
                try {
                    SharedPreferences p = getPrefs();
                    int x = overlayWebLastX, y = overlayWebLastY;
                    if (p != null) { x = p.getInt(PREF_POS_X, x); y = p.getInt(PREF_POS_Y, y); }
                    pos.put("x", x); pos.put("y", y);
                } catch (Exception ignored) {}
                obj.put("position", pos);

                // clipboard
                try {
                    ClipboardManager cm = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
                    String clip = "";
                    if (cm != null && cm.hasPrimaryClip()) {
                        ClipData cd = cm.getPrimaryClip();
                        if (cd != null && cd.getItemCount() > 0) {
                            CharSequence cs = cd.getItemAt(0).coerceToText(getContext());
                            clip = cs != null ? cs.toString() : "";
                        }
                    }
                    obj.put("clipboard", clip);
                } catch (Exception ignored) {}

                // categories tree (nested)
                JSONArray categories = new JSONArray();
                // Simple presets
                categories.put(new JSONObject()
                        .put("id", "summary").put("name", "总结").put("icon", "📝")
                        .put("prompts", new JSONArray()
                                .put("请你用简洁的中文总结以下内容：{input}")
                                .put("概括重点：{input}")
                                .put("提炼要点：{input}")
                        ));
                categories.put(new JSONObject()
                        .put("id", "rewrite").put("name", "改写").put("icon", "✍️")
                        .put("prompts", new JSONArray()
                                .put("将以下内容换一种说法：{input}")
                                .put("保持意思不变，重新表述：{input}")
                        ));
                categories.put(new JSONObject()
                        .put("id", "polish").put("name", "润色").put("icon", "✨")
                        .put("prompts", new JSONArray()
                                .put("润色并优化表达：{input}")
                                .put("让语气更自然友好：{input}")
                        ));

                // Persona with subcategories
                JSONObject persona = new JSONObject()
                        .put("id", "persona").put("name", "百变人设").put("icon", "👤")
                        .put("prompts", new JSONArray());
                JSONArray subs = new JSONArray();
                subs.put(new JSONObject()
                        .put("id", "wise-sister").put("name", "知性姐姐").put("icon", "👩‍🎓")
                        .put("prompts", new JSONArray()
                                .put("用温柔知性的语气回复：{input}")
                                .put("以关怀体贴的方式回应：{input}")
                        ));
                subs.put(new JSONObject()
                        .put("id", "funny-uncle").put("name", "幽默大叔").put("icon", "👨‍💼")
                        .put("prompts", new JSONArray()
                                .put("以幽默风趣的方式回复：{input}")
                                .put("结合生活经验幽默回应：{input}")
                        ));
                persona.put("subcategories", subs);
                categories.put(persona);

                obj.put("categoriesTree", categories);
                return obj.toString();
            } catch (Exception e) { return "{}"; }
        }
        @JavascriptInterface public void close() {
            new Handler(Looper.getMainLooper()).post(() -> hideOverlayWeb());
        }
        @JavascriptInterface public String pasteFromClipboard() {
            try {
                ClipboardManager cm = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
                if (cm != null && cm.hasPrimaryClip()) {
                    ClipData cd = cm.getPrimaryClip();
                    if (cd != null && cd.getItemCount() > 0) {
                        CharSequence cs = cd.getItemAt(0).coerceToText(getContext());
                        return cs != null ? cs.toString() : "";
                    }
                }
            } catch (Exception ignored) {}
            return "";
        }
        @JavascriptInterface public void copyToClipboard(String text) {
            try {
                ClipboardManager cm = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
                if (cm != null) {
                    ClipData clip = ClipData.newPlainText("overlay", text == null ? "" : text);
                    cm.setPrimaryClip(clip);
                }
            } catch (Exception ignored) {}
        }

        // Dynamically adjust drag exclusion regions (in physical px)
        @JavascriptInterface public void setDragExclusionLeft(int px) {
            try {
                final int val = Math.max(0, px);
                new Handler(Looper.getMainLooper()).post(() -> {
                    dragExcludeLeftPx = val;
                    try { Log.i("SystemOverlay", "setDragExclusionLeft px=" + val); } catch (Exception ignored) {}
                    // Top-left fixed handle mode: ignore dynamic exclusions
                });
            } catch (Exception ignored) {}
        }

        @JavascriptInterface public void setDragExclusionRight(int px) {
            try {
                final int val = Math.max(0, px);
                new Handler(Looper.getMainLooper()).post(() -> {
                    dragExcludeRightPx = val;
                    try { Log.i("SystemOverlay", "setDragExclusionRight px=" + val); } catch (Exception ignored) {}
                    // Top-left fixed handle mode: ignore dynamic exclusions
                });
            } catch (Exception ignored) {}
        }
        @JavascriptInterface public void sendMessage(String text) {
            try {
                pendingInput = text;
                pendingToggle = true; // ensure panel opens
                Context ctx = getContext();
                Intent launchIntent = ctx.getPackageManager().getLaunchIntentForPackage(ctx.getPackageName());
                if (launchIntent != null) {
                    launchIntent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
                    ctx.startActivity(launchIntent);
                }
                new Handler(Looper.getMainLooper()).postDelayed(() -> {
                    try { notifyListeners("overlayToggle", new JSObject()); } catch (Exception ignored) {}
                }, 200);
                new Handler(Looper.getMainLooper()).post(() -> hideOverlayWeb());
            } catch (Exception ignored) {}
        }
        @JavascriptInterface public void savePosition(int x, int y) {
            try {
                overlayWebLastX = x; overlayWebLastY = y;
                SharedPreferences p = getPrefs();
                if (p != null) p.edit().putInt(PREF_POS_X, x).putInt(PREF_POS_Y, y).apply();
            } catch (Exception ignored) {}
        }

        @JavascriptInterface public void showToast(String message) {
            try {
                final String msg = message == null ? "" : message;
                new Handler(Looper.getMainLooper()).post(() -> {
                    try { Toast.makeText(getContext().getApplicationContext(), msg, Toast.LENGTH_SHORT).show(); } catch (Exception ignored) {}
                });
            } catch (Exception ignored) {}
        }

        @JavascriptInterface public void hapticImpact(String style) {
            try {
                Vibrator v = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE);
                if (v == null || !v.hasVibrator()) return;
                long duration = 15; // light
                if (style != null) {
                    String s = style.toLowerCase();
                    if (s.contains("medium") || s.contains("default")) duration = 25;
                    else if (s.contains("heavy") || s.contains("strong")) duration = 40;
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    try { v.vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE)); } catch (Exception ignored) {}
                } else {
                    try { v.vibrate(duration); } catch (Exception ignored) {}
                }
            } catch (Exception ignored) {}
        }

        // Expose AI configuration to WebView (reads from SharedPreferences)
        @JavascriptInterface public String getAiConfig() {
            try {
                SharedPreferences p = getPrefs();
                String cfgStr = p != null ? p.getString(PREF_AI_CONFIG, null) : null;
                if (cfgStr == null || cfgStr.length() == 0) {
                    JSONObject obj = new JSONObject();
                    obj.put("configured", false);
                    return obj.toString();
                }
                try {
                    // Log snapshot with masked apiKey
                    JSONObject logObj = new JSONObject(cfgStr);
                    String k = logObj.optString("apiKey", "");
                    if (k != null && k.length() > 0) {
                        String masked = k.length() <= 8 ? "****" : k.substring(0,4) + "..." + k.substring(k.length()-2);
                        logObj.put("apiKey", masked);
                    }
                    Log.i("SystemOverlay", "getAiConfig() -> " + logObj.toString());
                } catch (Exception ignored) {}
                return cfgStr;
            } catch (Exception e) { return "{}"; }
        }

        // Allow WebView JS to update AI configuration directly
        @JavascriptInterface public void setAiConfig(String configJson) {
            try {
                if (configJson == null) return;
                SharedPreferences p = getPrefs();
                if (p == null) return;
                p.edit().putString(PREF_AI_CONFIG, configJson).apply();
                try {
                    JSONObject logObj = new JSONObject(configJson);
                    String k = logObj.optString("apiKey", "");
                    if (k != null && k.length() > 0) {
                        String masked = k.length() <= 8 ? "****" : k.substring(0,4) + "..." + k.substring(k.length()-2);
                        logObj.put("apiKey", masked);
                    }
                    Log.i("SystemOverlay", "[JS] setAiConfig saved " + logObj.toString());
                } catch (Exception ignored) {}
            } catch (Exception ignored) {}
        }

        // Placeholder for future native AI text generation (non-stream)
        // For now returns empty string to indicate unavailable.
        @JavascriptInterface public String generateText(String finalPrompt) {
            try {
                return "";
            } catch (Exception e) { return ""; }
        }

        // New: native candidate generation via OpenAI-compatible API
        @JavascriptInterface public void generateCandidates(String text, String optsJson) {
            try {
                final String userText = text == null ? "" : text;
                final JSONObject opts = (optsJson != null && optsJson.length() > 0) ? new JSONObject(optsJson) : new JSONObject();

                // Debug: entry and input
                try {
                    Log.i("SystemOverlay", "generateCandidates() called, textLen=" + userText.length() + ", opts=" + opts.toString());
                } catch (Exception ignored) {}

                SharedPreferences p = getPrefs();
                String cfgStr = p != null ? p.getString(PREF_AI_CONFIG, null) : null;
                final JSONObject cfg = cfgStr != null ? new JSONObject(cfgStr) : new JSONObject();

                // Debug: config presence
                try {
                    Log.i("SystemOverlay", "generateCandidates() prefs cfg present=" + (cfgStr != null) + ", len=" + (cfgStr != null ? cfgStr.length() : 0));
                } catch (Exception ignored) {}

                final String apiKey = cfg.optString("apiKey", "");
                final String model = cfg.optString("model", "gpt-4o-mini");
                String baseUrl = cfg.optString("baseUrl", "https://api.openai.com");
                final String provider = cfg.optString("provider", "");
                final double temperature = cfg.has("temperature") ? cfg.optDouble("temperature", 0.7) : opts.optDouble("temperature", 0.7);
                final double topP = cfg.has("topP") ? cfg.optDouble("topP", 1.0) : opts.optDouble("topP", 1.0);
                final int k = opts.optInt("k", 5);

                // Debug: config snapshot (mask apiKey)
                try {
                    String maskedKey;
                    if (apiKey == null) maskedKey = "null";
                    else if (apiKey.length() <= 8) maskedKey = "****";
                    else maskedKey = apiKey.substring(0, 4) + "..." + apiKey.substring(apiKey.length() - 2);
                    Log.i("SystemOverlay", "generateCandidates() cfg provider=" + provider + ", baseUrl=" + baseUrl + ", model=" + model + ", temp=" + temperature + ", topP=" + topP + ", k=" + k + ", apiKey=" + maskedKey);
                } catch (Exception ignored) {}

                if (apiKey == null || apiKey.isEmpty()) {
                    evalInOverlayWeb("window.__overlay_onCandidatesError && window.__overlay_onCandidatesError({ code:'no_api_key', message:'Missing API key' })");
                    return;
                }

                // Detect Gemini/Google provider or baseUrl
                final String provLower = provider == null ? "" : provider.toLowerCase();
                final boolean isGemini = provLower.contains("google") || provLower.contains("gemini") || (baseUrl != null && baseUrl.toLowerCase().contains("generativelanguage.googleapis.com"));

                if (isGemini) {
                    try { Log.i("SystemOverlay", "generateCandidates() branch = GEMINI"); } catch (Exception ignored) {}
                    // Build Google Generative Language API URL (respect baseUrl path if provided)
                    String glBase = (baseUrl != null && baseUrl.length() > 0) ? baseUrl : "https://generativelanguage.googleapis.com";
                    if (glBase.endsWith("/")) glBase = glBase.substring(0, glBase.length() - 1);
                    String apiUrl;
                    try {
                        String lower = glBase.toLowerCase();
                        boolean hasV1beta = lower.contains("/v1beta");
                        boolean hasModels = lower.contains("/v1beta/models");
                        if (hasV1beta) {
                            if (hasModels) {
                                apiUrl = glBase + "/" + model + ":generateContent";
                            } else {
                                apiUrl = glBase + "/models/" + model + ":generateContent";
                            }
                        } else {
                            apiUrl = glBase + "/v1beta/models/" + model + ":generateContent";
                        }
                    } catch (Exception _e) {
                        apiUrl = glBase + "/v1beta/models/" + model + ":generateContent";
                    }
                    try { Log.i("SystemOverlay", "GEMINI request url=" + apiUrl); } catch (Exception ignored) {}

                    // Use the passed-in text as the final prompt (already contains formatting instructions)
                    final String prompt = userText;
                    // Debug: prompt preview
                    try {
                        String prev = prompt.replaceAll("\\s+", " ");
                        if (prev.length() > 200) prev = prev.substring(0, 200);
                        Log.i("SystemOverlay", "GEMINI prompt preview len=" + prompt.length() + " preview=\"" + prev + "\"");
                    } catch (Exception ignored) {}

                    JSONObject body = new JSONObject();
                    JSONArray contents = new JSONArray();
                    JSONObject userContent = new JSONObject();
                    userContent.put("role", "user");
                    JSONArray parts = new JSONArray();
                    parts.put(new JSONObject().put("text", prompt));
                    userContent.put("parts", parts);
                    contents.put(userContent);
                    body.put("contents", contents);
                    // generationConfig
                    JSONObject gen = new JSONObject();
                    gen.put("temperature", temperature);
                    gen.put("topP", topP);
                    body.put("generationConfig", gen);

                    final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
                    final Request req = new Request.Builder()
                            .url(apiUrl)
                            .addHeader("x-goog-api-key", apiKey)
                            .addHeader("Content-Type", "application/json")
                            .post(RequestBody.create(body.toString(), JSON))
                            .build();

                    new Thread(() -> {
                        try (Response resp = getHttp().newCall(req).execute()) {
                            try { Log.i("SystemOverlay", "GEMINI http status=" + resp.code()); } catch (Exception ignored) {}
                            if (!resp.isSuccessful()) {
                                String emsg = "HTTP " + resp.code();
                                try {
                                    String bodyS = resp.body() != null ? resp.body().string() : "";
                                    Log.w("SystemOverlay", "GEMINI http_error bodyLen=" + (bodyS != null ? bodyS.length() : 0));
                                } catch (Exception ignored) {}
                                evalInOverlayWeb("window.__overlay_onCandidatesError && window.__overlay_onCandidatesError({ code:'http_error', message:'" + emsg + "' })");
                                return;
                            }
                            String respStr = resp.body() != null ? resp.body().string() : "";
                            try { Log.i("SystemOverlay", "GEMINI resp len=" + (respStr != null ? respStr.length() : 0)); } catch (Exception ignored) {}
                            JSONObject rj = new JSONObject(respStr);
                            JSONArray candidates = rj.optJSONArray("candidates");
                            if (candidates == null || candidates.length() == 0) {
                                evalInOverlayWeb("window.__overlay_onCandidatesError && window.__overlay_onCandidatesError({ code:'no_candidates', message:'No candidates returned' })");
                                return;
                            }
                            // Take first candidate's text parts and attempt to parse as JSON
                            JSONObject first = candidates.getJSONObject(0);
                            JSONObject contentObj = first.optJSONObject("content");
                            StringBuilder sb = new StringBuilder();
                            if (contentObj != null) {
                                JSONArray cparts = contentObj.optJSONArray("parts");
                                if (cparts != null) {
                                    for (int i = 0; i < cparts.length(); i++) {
                                        JSONObject part = cparts.optJSONObject(i);
                                        if (part != null) {
                                            String t = part.optString("text", "");
                                            if (t != null) sb.append(t);
                                        }
                                    }
                                }
                            }
                            String content = sb.toString();
                            if (content == null) content = "";
                            // Debug: content preview
                            try {
                                String prev = content.replaceAll("\\s+", " ");
                                if (prev.length() > 200) prev = prev.substring(0, 200);
                                Log.i("SystemOverlay", "GEMINI content preview len=" + content.length() + " preview=\"" + prev + "\"");
                            } catch (Exception ignored) {}

                            JSONObject payload;
                            try { payload = new JSONObject(content); }
                            catch (Exception pe) {
                                payload = new JSONObject();
                                JSONArray arr = new JSONArray();
                                if (content.length() > 0) arr.put(content);
                                payload.put("candidates", arr);
                            }

                            final String jsonLiteral = payload.toString();
                            try {
                                int candCount = payload.optJSONArray("candidates") != null ? payload.optJSONArray("candidates").length() : -1;
                                Log.i("SystemOverlay", "GEMINI deliver to JS, candidates=" + candCount);
                            } catch (Exception ignored) {}
                            evalInOverlayWeb("window.__overlay_onCandidates && window.__overlay_onCandidates(" + jsonLiteral + ")");
                        } catch (Exception e) {
                            String msg = e.getMessage() == null ? "error" : e.getMessage().replace("\n", " ");
                            try { Log.e("SystemOverlay", "GEMINI exception: " + msg); } catch (Exception ignored) {}
                            evalInOverlayWeb("window.__overlay_onCandidatesError && window.__overlay_onCandidatesError({ code:'exception', message:'" + msg.replace("'", "\\'") + "' })");
                        }
                    }).start();
                    return;
                }

                String apiUrl;
                if (baseUrl.endsWith("/v1") || baseUrl.endsWith("/v1/")) {
                    apiUrl = baseUrl.replaceAll("/$", "") + "/chat/completions";
                } else if (baseUrl.endsWith("/")) {
                    apiUrl = baseUrl + "v1/chat/completions";
                } else {
                    apiUrl = baseUrl + "/v1/chat/completions";
                }
                try { Log.i("SystemOverlay", "OPENAI request url=" + apiUrl); } catch (Exception ignored) {}

                // Build OpenAI-compatible chat completion request using the passed-in final prompt
                JSONObject body = new JSONObject();
                body.put("model", model);
                body.put("temperature", temperature);
                body.put("top_p", topP);
                JSONArray messages = new JSONArray();
                messages.put(new JSONObject().put("role", "user").put("content", userText));
                body.put("messages", messages);
                // Debug: prompt preview
                try {
                    String prev = userText.replaceAll("\\s+", " ");
                    if (prev.length() > 200) prev = prev.substring(0, 200);
                    Log.i("SystemOverlay", "OPENAI prompt preview len=" + userText.length() + " preview=\"" + prev + "\"");
                } catch (Exception ignored) {}

                final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
                final Request req = new Request.Builder()
                        .url(apiUrl)
                        .addHeader("Authorization", "Bearer " + apiKey)
                        .addHeader("Content-Type", "application/json")
                        .post(RequestBody.create(body.toString(), JSON))
                        .build();

                new Thread(() -> {
                    try (Response resp = getHttp().newCall(req).execute()) {
                        try { Log.i("SystemOverlay", "OPENAI http status=" + resp.code()); } catch (Exception ignored) {}
                        if (!resp.isSuccessful()) {
                            String emsg = "HTTP " + resp.code();
                            try {
                                String bodyS = resp.body() != null ? resp.body().string() : "";
                                Log.w("SystemOverlay", "OPENAI http_error bodyLen=" + (bodyS != null ? bodyS.length() : 0));
                            } catch (Exception ignored) {}
                            evalInOverlayWeb("window.__overlay_onCandidatesError && window.__overlay_onCandidatesError({ code:'http_error', message:'" + emsg + "' })");
                            return;
                        }
                        String respStr = resp.body() != null ? resp.body().string() : "";
                        try { Log.i("SystemOverlay", "OPENAI resp len=" + (respStr != null ? respStr.length() : 0)); } catch (Exception ignored) {}
                        JSONObject rj = new JSONObject(respStr);
                        JSONArray choices = rj.optJSONArray("choices");
                        if (choices == null || choices.length() == 0) {
                            evalInOverlayWeb("window.__overlay_onCandidatesError && window.__overlay_onCandidatesError({ code:'no_choices', message:'No choices returned' })");
                            return;
                        }
                        JSONObject first = choices.getJSONObject(0);
                        JSONObject message = first.optJSONObject("message");
                        String content = message != null ? message.optString("content", "") : first.optString("text", "");
                        if (content == null) content = "";
                        // Debug: content preview
                        try {
                            String prev = content.replaceAll("\\s+", " ");
                            if (prev.length() > 200) prev = prev.substring(0, 200);
                            Log.i("SystemOverlay", "OPENAI content preview len=" + content.length() + " preview=\"" + prev + "\"");
                        } catch (Exception ignored) {}

                        JSONObject payload;
                        try { payload = new JSONObject(content); }
                        catch (Exception pe) {
                            payload = new JSONObject();
                            JSONArray arr = new JSONArray();
                            if (content.length() > 0) arr.put(content);
                            payload.put("candidates", arr);
                        }

                        final String jsonLiteral = payload.toString();
                        try {
                            int candCount = payload.optJSONArray("candidates") != null ? payload.optJSONArray("candidates").length() : -1;
                            Log.i("SystemOverlay", "OPENAI deliver to JS, candidates=" + candCount);
                        } catch (Exception ignored) {}
                        evalInOverlayWeb("window.__overlay_onCandidates && window.__overlay_onCandidates(" + jsonLiteral + ")");
                    } catch (Exception e) {
                        String msg = e.getMessage() == null ? "error" : e.getMessage().replace("\n", " ");
                        try { Log.e("SystemOverlay", "OPENAI exception: " + msg); } catch (Exception ignored) {}
                        evalInOverlayWeb("window.__overlay_onCandidatesError && window.__overlay_onCandidatesError({ code:'exception', message:'" + msg.replace("'", "\\'") + "' })");
                    }
                }).start();
            } catch (Exception e) {
                String msg = e.getMessage() == null ? "error" : e.getMessage().replace("\n", " ");
                try { Log.e("SystemOverlay", "generateCandidates() bad_args: " + msg); } catch (Exception ignored) {}
                evalInOverlayWeb("window.__overlay_onCandidatesError && window.__overlay_onCandidatesError({ code:'bad_args', message:'" + msg.replace("'", "\\'") + "' })");
            }
        }

        // Optional: clipboard history (not implemented). Returns empty array.
        @JavascriptInterface public String getClipboardHistory() { return "[]"; }
    }

    @PluginMethod
    public void hideSystemOverlay(PluginCall call) {
        getActivity().runOnUiThread(() -> {
            try {
                if (overlayView != null && windowManager != null) {
                    windowManager.removeView(overlayView);
                    overlayView = null;
                }
                call.resolve();
            } catch (Exception e) {
                call.reject("Failed to hide overlay: " + e.getMessage());
            }
        });
    }

    @PluginMethod
    public void updateOverlayPosition(PluginCall call) {
        getActivity().runOnUiThread(() -> {
            try {
                if (overlayView != null && windowManager != null && params != null) {
                    params.x = call.getInt("x", params.x);
                    params.y = call.getInt("y", params.y);
                    windowManager.updateViewLayout(overlayView, params);
                }
                call.resolve();
            } catch (Exception e) {
                call.reject("Failed to update position: " + e.getMessage());
            }
        });
    }

    @PluginMethod
    public void getAndClearPendingToggle(PluginCall call) {
        try {
            JSObject ret = new JSObject();
            ret.put("pending", pendingToggle);
            pendingToggle = false;
            call.resolve(ret);
        } catch (Exception e) {
            call.reject("Failed to get pending toggle: " + e.getMessage());
        }
    }

    @PluginMethod
    public void getAndClearPendingInput(PluginCall call) {
        try {
            JSObject ret = new JSObject();
            if (pendingInput != null && pendingInput.length() > 0) {
                ret.put("text", pendingInput);
            }
            pendingInput = null;
            call.resolve(ret);
        } catch (Exception e) {
            call.reject("Failed to get pending input: " + e.getMessage());
        }
    }

    @PluginMethod
    public void setAiConfig(PluginCall call) {
        try {
            String configJson = call.getString("configJson");
            if (configJson == null) {
                try { Log.e("SystemOverlay", "setAiConfig() missing configJson"); } catch (Exception ignored) {}
                call.reject("configJson is required");
                return;
            }
            try {
                // Log snapshot of incoming config (mask apiKey)
                JSONObject cfgLog = new JSONObject(configJson);
                String apiKey = cfgLog.optString("apiKey", "");
                if (apiKey != null && apiKey.length() > 0) {
                    String masked = apiKey.length() <= 8 ? "****" : apiKey.substring(0,4) + "..." + apiKey.substring(apiKey.length()-2);
                    cfgLog.put("apiKey", masked);
                }
                Log.i("SystemOverlay", "setAiConfig() incoming=" + cfgLog.toString());
            } catch (Exception ignored) {}
            SharedPreferences p = getPrefs();
            if (p == null) {
                try { Log.e("SystemOverlay", "setAiConfig() SharedPreferences unavailable"); } catch (Exception ignored) {}
                call.reject("SharedPreferences unavailable");
                return;
            }
            p.edit().putString(PREF_AI_CONFIG, configJson).apply();
            try { Log.i("SystemOverlay", "setAiConfig() saved, len=" + configJson.length()); } catch (Exception ignored) {}
            call.resolve();
        } catch (Exception e) {
            try { Log.e("SystemOverlay", "setAiConfig() error: " + e.getMessage()); } catch (Exception ignored) {}
            call.reject("Failed to set AI config: " + e.getMessage());
        }
    }

    @PluginMethod
    public void getOverlayAiConfig(PluginCall call) {
        try {
            SharedPreferences p = getPrefs();
            JSObject ret = new JSObject();
            if (p != null) {
                String cfg = p.getString(PREF_AI_CONFIG, null);
                try { Log.i("SystemOverlay", "getOverlayAiConfig() present=" + (cfg != null) + ", len=" + (cfg != null ? cfg.length() : 0)); } catch (Exception ignored) {}
                if (cfg != null) ret.put("configJson", cfg);
            }
            call.resolve(ret);
        } catch (Exception e) {
            try { Log.e("SystemOverlay", "getOverlayAiConfig() error: " + e.getMessage()); } catch (Exception ignored) {}
            call.reject("Failed to get AI config: " + e.getMessage());
        }
    }
}